Better Programming

Advice for programmers.

Follow publication

Adding Web3 to Our NextJS TypeScript Project

Will Kelly
Better Programming
Published in
5 min readFeb 23, 2022

--

If you haven’t seen my last two posts setting up our project thus far, check out part one & part two. We’ll be using the same repo on a new web3-eth branch here.

If you aren’t familiar with Ethereum or Metamask, definitely check them out. Metamask is a browser wallet that lets you interact with EVM-compatible blockchains. If you have significant funds, I highly recommend getting a Ledger — and connecting it to your Metamask to make it convenient to use dApps.

Make sure you have the Metamask extension installed in your browser before you get started.

If you want to see a quick demo of what we are making, check it out here.

Let’s install some tools for handling Web3 integration with our project:

yarn add @walletconnect/web3-provider ethers web3modal

walletconnect lets us connect wallets to our app via an open protocol.

ethers is a modern javascript/typescript Web3 library. I prefer it to other options like web3.js

Finally, web3modal is a library that allows us to support different wallet providers like Metamask, WalletConnect, or Coinbase Wallet (among many others) in a generalized way. You’ve probably interacted with it as a user if you’ve ever used a dApp before.

One more thing we will need is an Infura API key. This will let us access an Ethereum node over a simple API.

Fun fact: Metamask actually uses Infura behind the scenes.

Sign up here if you don’t already have an account: https://infura.io/

Then “Create New Project”, select “Ethereum”, then copy over your PROJECT_ID for use in our app.

If you wanted to restrict access, there’s also a security tab where you can enable JWT auth, secrets, allowlists, or rate limits. We will skip that for now.

Since we now have some secrets, and NextJS provides some built-in support for env variables, create an env file to store these.

touch .env.local

NEXT_PUBLIC_INFURA_ID=PASTE_YOUR_ID_HERE

We will structure our Web3 connector as a hook utilizing a reducer, and keep it within a context. It’s quite a few moving parts, but worthwhile to be deliberate about managing the state.

There is a decent template here we can build off of, with a few extensions for a more fully-fledged app.

Go ahead and initialize three folders in your Next app:

mkdir hooks
mkdir reducers
mkdir context

Within reducers, init a Web3Provider.ts file with three distinct parts:

1. State (& an initial state)

The null and undefined may seem a bit strange, but we to start with a ‘blank’ initial state, and the Web3 variables can be undefined in some scenarios. For example, the address variable when Metamask itself is injected into the browser window but no specific address has connected. This will become more clear when we get into ethers’ signer vs provider functions.

2. Action

3. Reducer

Actions and reducers are fairly straightforward, so I’ll proceed and export these in reducers/index.ts with:

export type { Web3ProviderState, Web3Action } from './Web3Provider'
export { web3InitialState, web3Reducer } from './Web3Provider'

Up next, we’ll make the hook in /hooks/Web3Client.ts Up first, we will initialize web3modal:

Really just two things here, providerOptions lets you use different types of Web3 wallets. There’s a full list of built-in providers here, and you can even add custom providers by following the documentation here.

The item of note is possible null and the if statement checking the window, which just handles the server-side rendering condition. If we were using vanilla React rather than Next, we could exclude this.

Now let’s get into the main hook…

It’s a bit lengthy, but this is mostly boilerplate around keeping the state in sync with the provider while updating the reducer with each of the actions we defined above. It’ll be a bit easier to understand when we see it from the user’s perspective.

Let’s go ahead and see this hook in action.

In components, create a components/Web3Button.tsx that will let us utilize the connect and disconnect functions for our wallet within the app.

Also, create a Web3Address.tsx in components so we can see an example of reading some basic states:

and then edit our index.tsx to add the button and address component:

Now we have a nice little dApp. Before we get this thing launched, let’s add react-tostify to get a little more insight into the status of our provider.

yarn add react-toastify

Then drop it into our _app.tsx like so:

And finally, drop in some toasts to our Web3Client.ts:

import { toast } from 'react-toastify'...
// before 'SET_WEB3_PROVIDER' in connect
toast.success('Connected to Web3')
// before 'RESET_WEB3_PROVIDER' in disconnect
toast.error('Disconnected from Web3')
// before 'SET_ADDRESS' in handleAccountsChanged
toast.info('Changed Web3 Account')
// before 'window.location.reload()' in handleChainChanged
toast.info('Web3 Network Changed')

Then start it up!

If you feel out of sync at all, you can view the current state here.

Notice anything off? (Other than styling and the neglected about page…)

Double Connect!
Out of Sync!

Having the toast may seem a bit extra, but it illustrates a good lesson about using hooks. When using the hook, you can see that we are reconnecting multiple times — re-triggering the connect logic on every hook use.

We can also see that we disconnected, yet our Web3Address.tsx component hasn’t noticed we don’t have a connected address!

The context adds a little bit of extra code, but it is nice (or even necessary) to have this stored state in sync and available to our downstream components.

So… let’s initialize a context in /context/Web3Context.tsx:

We’ll have a bit of an issue with the web3InitialState as the connect & disconnect functions need an initial state.

In reducers/Web3Provider.ts update:

export type Web3ProviderState = {
...
connect: (() => Promise<void>) | null
disconnect: (() => Promise<void>) | null
}
export const web3InitialState: Web3ProviderState = {
...
connect: null,
disconnect: null,
}

And in our components, switch to using the useWeb3Context() instead of our useWeb3() hook.

And finally, make sure you wrapped _app.tsx with the Web3ContextProvider

Then we should be good to go — and your local version should have the stale state issues fixed.

Don’t forget to also add your Infura PROJECT_ID to Vercel’s env if you are going to deploy!

Thanks for reading. Check out the demo at: https://boilerplate-next-eth-context.vercel.app Our up-to-date branch: https://github.com/wk0/boilerplate-next/tree/web3-eth

Want to Connect?Also check out my website: https://wk0.dev

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Write a response